package at.fhooe.stefanirndorfer.labeling;

import static at.fhooe.stefanirndorfer.utilities.Utilities.NW;
import static at.fhooe.stefanirndorfer.utilities.Utilities.E;
import static at.fhooe.stefanirndorfer.utilities.Utilities.N;
import static at.fhooe.stefanirndorfer.utilities.Utilities.NE;
import static at.fhooe.stefanirndorfer.utilities.Utilities.S;
import static at.fhooe.stefanirndorfer.utilities.Utilities.SE;
import static at.fhooe.stefanirndorfer.utilities.Utilities.SW;
import static at.fhooe.stefanirndorfer.utilities.Utilities.W;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.Random;

import at.fhooe.stefanirndorfer.connectivity.Connection;
import at.fhooe.stefanirndorfer.connectivity.ConnectivityData;
import at.fhooe.stefanirndorfer.game.Game;
import at.fhooe.stefanirndorfer.logging.Log;
import at.fhooe.stefanirndorfer.materials.Material;
import at.fhooe.stefanirndorfer.quadtree.QuadTreeNode;
import at.fhooe.stefanirndorfer.walls.Wall;

public class Region {
	private static int numRegions = 0;

	private String id;
	private float[]color;
	private ArrayList<QuadTreeNode> leafs;
	private Region destRegion;
	
	
	private ArrayList<Connection> connections;

	private ArrayList<QuadTreeNode> borderLeafs;
	
	public Region(){
		id = this.getClass().getSimpleName() + System.nanoTime();
		leafs = new ArrayList<QuadTreeNode>();
		randomizeColor();
		//Log.info(this, "new Region built!");
		destRegion = null;
		numRegions++;
		Game.getInstance().getRegionManager().addRegion(this);
		connections = new ArrayList<Connection>();
		borderLeafs = new ArrayList<QuadTreeNode>();
	}
	
	
	private float[] randomizeColor() {
		this.color = new float[3];
		Random rand = new Random();
		for (int i = 0; i < color.length; i++) {
			color[i] = rand.nextFloat();
		}
		return color;
	}


	public void addConnection(Connection c){
		connections.add(c);
	}
	
	public void removeConnectionsToRegion(Region r){
		String rId = r.getId();
		
		Iterator<Connection> it = connections.iterator();
		while(it.hasNext()){
			if(it.next().getAdjRegion().getId().equals(rId))
				it.remove();
		}
	}
	
	/**
	 * searches the graph within the audible set
	 * @param r
	 * @return
	 */
	public Connection getBestConnectionToRegion(Region r){
		String rId = r.getId();
		Connection c = null;
		
		for (int i = 0; i < connections.size(); i++) {
			Connection currC = connections.get(i);
			if(currC.getAdjRegion().getId().equals(r.getId())){
				//TODO
			}else{
				//TODO
			}
		}
		
		
		return c;
	}
	
	
	public void addLeaf(QuadTreeNode p) {
		leafs.add(p);
	}


	public String getId() {
		return id;
	}


	public float[] getColor() {
		return color;
	}


	public ArrayList<QuadTreeNode> getLeafs() {
		return leafs;
	}


	public void moveRegionTo(Region currDestReg) {
		System.out.println("Hallo, i am Region "+id + " and i move to "+currDestReg.id);
		
		//if (leafs.size() > 0) {
			if (destRegion == null) {
				destRegion = currDestReg;

				Iterator<QuadTreeNode> it = leafs.iterator();
				while (it.hasNext()) {
					QuadTreeNode n = it.next();
					n.setLabel(currDestReg);
					currDestReg.addLeaf(n);
					it.remove();
				}
				
				
			}else{
				//if(!this.getId().equals(currDestReg.getId())){ // avoid circular moving
				destRegion.moveRegionTo(currDestReg);
				
//				}
//				else{
//					Log.info(this, "merging skipped");
//				}
			}
			numRegions--;
			Log.info(this, "NumRegioins: " + numRegions);
			Game.getInstance().getRegionManager().removeRegion(this);

		//}
		
	}


	public synchronized void removeLeaf(QuadTreeNode toRemove) {
		for(QuadTreeNode n : leafs){
			if(n.getId().equals(toRemove.getId())){
				leafs.remove(n);
				break;
			}
		}
		
	}


	@Override
	public String toString() {
		String s = "";
		s += "Region: Id: " + id;
		return s;
	}
	
	public synchronized void  findBorderLeafs(){
		for(QuadTreeNode n : leafs){
			checkIfBorderLeaf(n);
		}
	}


	/**
	 * this method evolves in N, E, S, W direction
	 * @param n 
	 * @return
	 */
	private void checkIfBorderLeaf(QuadTreeNode n) {
		QuadTreeNode p = null;
		
			p = Labeling_Manager.get_Equal_Adj_Neighbour(n, N);			
			if(p != null){
				//TODO refactor to use reflectedDirection()
				short[] quadrants = reflectDirection(N);
				checkForAdjacentWall(p, SE, SW, n, N);
			}
			
			p = Labeling_Manager.get_Equal_Adj_Neighbour(n, E);			
			if(p != null){
				checkForAdjacentWall(p, SW, NW, n, E);
			}
			
			p = Labeling_Manager.get_Equal_Adj_Neighbour(n, S);			
			if(p != null){
				checkForAdjacentWall(p, NE, NW, n, S);
			}
			
			p = Labeling_Manager.get_Equal_Adj_Neighbour(n, W);			
			if(p != null){
				checkForAdjacentWall(p, SE, NE, n, W);
			}
			
	}

	/**
	 * hardcoded utility function returns the two quadrants reflected over a direction
	 * @param n
	 * @return
	 */
	private short[] reflectDirection(short n) {
		switch(n){
		case N:
			return new short[]{SE, SW};
		case S:
			return new short[]{NE, NW};
		case W:
			return new short[]{NE, SE};
		case E:
			return new short[]{NW, SW};
		}
		return null;
	}


	private void checkForAdjacentWall(QuadTreeNode r, short q1, short q2,
			QuadTreeNode p, short dir) {
		if(r.getChildNodes()[NW] != null){
			checkForAdjacentWall(r.getChildNodes()[q1], q1, q2, p, dir);
			checkForAdjacentWall(r.getChildNodes()[q2], q1, q2, p, dir);
		}else{
			if(r.getMaterial() != null){
				p.setIsBorderLeaf(true);
				p.setConnectivityData(new ConnectivityData(dir, 0.0f, 0.0f, null));
				borderLeafs.add(p);
			}
		}
		
	}


	public synchronized void removeAllLeafs() {
		if(leafs.size()>=1){
			for (int i = leafs.size()-1 ; i <= 0; i--) {
				leafs.remove(0);
			}
			
		}
	}


	public void calculateApertures() {
		for(QuadTreeNode n : borderLeafs){
			exploreDirection(n);
			findBestConnections(n);
		}
		
	}


	private void findBestConnections(QuadTreeNode n) {
		ArrayList<Connection> conn = n.getNodeConnections();
		Iterator<Connection> itConn = conn.iterator();
		while(itConn.hasNext()){
			Connection connection = itConn.next();
			Region currReg = connection.getAdjRegion();
			boolean regionAlreadyExists = false;
			
			Iterator<Connection> it = connections.iterator();
			while(it.hasNext()){
				Connection c = it.next();
				if(c.getAdjRegion().getId().equals(currReg)){
					regionAlreadyExists = true;
					
					//for now, we simply concentrate on LF-Gain
					if(c.getLf_gain() < connection.getLf_gain()){
						connections.add(connection);
					}else{
						itConn.remove();
					}
				}
			}
			if(!regionAlreadyExists){
				connections.add(connection);
			}
		}
		n.setNodeConnections(conn);
	}


	private void exploreDirection(QuadTreeNode n) {
		short[] rQs = reflectDirection(n.getConnectivityData().getDir());
		
			QuadTreeNode q = Labeling_Manager.get_Equal_Adj_Neighbour(n, n.getConnectivityData().getDir());
			if(q != null){
				sumAdjacent(q, rQs[0], rQs[1], n, new Connection(destRegion, 1.0f, 1.0f, 0.0, 0.0));
			}else{
				//edge of the world
				n.setConnectivityData(null);
			}
		}


	private void sumAdjacent(QuadTreeNode q, short q1, short q2, QuadTreeNode n, Connection currConn) {
		if(q.getChildNodes()[NW] != null){
			sumAdjacent(q.getChildNodes()[q1], q1, q2, n, currConn);
			Connection newConn = currConn.copyConnection();
			sumAdjacent(q.getChildNodes()[q2], q2, q1, n, newConn);
		}else{
			if(q.getMaterial() == null){
				if(q.getLabel().getId().equals(n.getLabel().getId())){
					//this is a connection to the same region and does not count
					currConn = null;
					return;
				}else{
					currConn.setAdjRegion(q.getLabel());
					n.addNodeConnection(currConn);
				}
			}else{
				// we have to add the damping of the current element ...
				Material m = q.getMaterial();
				float hf_gain = m.computeHFAtt(currConn.getHf_gain(), q.getSize());
				float lf_gain = m.computeHFAtt(currConn.getLf_gain(), q.getSize());
				if(hf_gain <= 0.0 && lf_gain <= 0.0){
					currConn = null;
					return;
				}else{
					currConn.setHf_gain(hf_gain);
					currConn.setLf_gain(lf_gain);
				}
				QuadTreeNode r = Labeling_Manager.get_Equal_Adj_Neighbour(q, n.getConnectivityData().getDir());
				if(r != null){
					sumAdjacent(r, q1, q2, n, currConn);
				}
				else{
					return;
				}
			}
		}
		
	}
	
	
	
	
	
}
